#include "spi.h"

#include <stdio.h>
#include <string.h>

#include "cfg.h"

void delay_us(uint32_t us) {
    for (volatile uint32_t i = 0; i < (us * 25); i++) {
        __NOP();
    }
}
void spi_flash_sector_erase(uint32_t sector_addr);
void MX_SPI1_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    SPI_CS_1;
    SPI_SCK_0;

#if 0  // test
		printf("spi flash deviceid: %x\r\n", get_DeviceId());
		
		spi_flash_buffer_write((uint8_t* )"hello world", 0x0, 11);
		uint8_t buff[32] = {0};
		get_ReadDataBytes(0, buff, 20);
		buff[11] = '\0';
		printf("spi read buff:%s\r\n", buff);
#endif
}

uint8_t SPI_ReadWriteByte(uint8_t txData) {
    uint8_t i;
    uint8_t rxData = 0;

    for (i = 0; i < 8; i++) {
        SPI_SCK_0;
        delay_us(1);
        if (txData & 0x80) {
            SPI_MOSI_1;
        } else {
            SPI_MOSI_0;
        }
        txData <<= 1;
        delay_us(1);

        SPI_SCK_1;
        delay_us(1);
        rxData <<= 1;
        if (SPI_READ_MISO) {
            rxData |= 0x01;
        }
        delay_us(1);
    }
    SPI_SCK_0;

    return rxData;
}

union GD32_UINT32_DATA_TYPE {
    uint8_t b[4];
    uint32_t Uint32_Data;
};

uint8_t spi_SendRcvByte(uint8_t TxData) {
    uint8_t Rxdata = SPI_ReadWriteByte(TxData);
    return Rxdata;
}

uint16_t get_DeviceId(void) {
    uint16_t dev_id = 0;

    SPI_FLASH_NSS_ENABLE;

    spi_SendRcvByte(GET_DEVICE_ID);
    spi_SendRcvByte(DUMMY_BYTE);
    spi_SendRcvByte(DUMMY_BYTE);
    spi_SendRcvByte(DUMMY_BYTE);

    dev_id = spi_SendRcvByte(DUMMY_BYTE) << 8;
    dev_id |= spi_SendRcvByte(DUMMY_BYTE);

    SPI_FLASH_NSS_DISENABLE;

    return dev_id;
}

void get_Identifica(uint8_t *data_out) {
    SPI_FLASH_NSS_ENABLE;

    spi_SendRcvByte(GET_IDENTIFICATION);
    data_out[0] = spi_SendRcvByte(DUMMY_BYTE);
    data_out[1] = spi_SendRcvByte(DUMMY_BYTE);
    data_out[2] = spi_SendRcvByte(DUMMY_BYTE);

    SPI_FLASH_NSS_DISENABLE;
}

void get_ReadDataBytes(uint32_t dest_addr, uint8_t *data, uint32_t len) {
    SPI_FLASH_NSS_ENABLE;

    spi_SendRcvByte(CMD_READ_DATA_BYTES);
    spi_SendRcvByte((uint8_t)(dest_addr >> 16));
    spi_SendRcvByte((uint8_t)(dest_addr >> 8));
    spi_SendRcvByte((uint8_t)(dest_addr));

    while (len--) {
        *data = spi_SendRcvByte(DUMMY_BYTE);
        data++;
    }

    SPI_FLASH_NSS_DISENABLE;
}

void spi_flash_write_enable(void) {
    SPI_FLASH_NSS_ENABLE;
    spi_SendRcvByte(CMD_WRITE_ENABLE);
    SPI_FLASH_NSS_DISENABLE;
}

void spi_flash_wait_for_write_end(void) {
    uint8_t flash_status = 0;

    SPI_FLASH_NSS_ENABLE;
    spi_SendRcvByte(CMD_READ_STA_REG);

    do {
        flash_status = spi_SendRcvByte(DUMMY_BYTE);
    } while ((flash_status & WIP_FLAG) == SET);

    SPI_FLASH_NSS_DISENABLE;
}

void spi_flash_sector_erase(uint32_t sector_addr) {
    union GD32_UINT32_DATA_TYPE addr;

    addr.Uint32_Data = sector_addr;

    spi_flash_write_enable();

    SPI_FLASH_NSS_ENABLE;
    spi_SendRcvByte(CMD_SECTOR_ERASE);
    spi_SendRcvByte(addr.b[2]);
    spi_SendRcvByte(addr.b[1]);
    spi_SendRcvByte(addr.b[0]);
    SPI_FLASH_NSS_DISENABLE;
    spi_flash_wait_for_write_end();
}

static void spi_flash_page_write(uint8_t *pbuffer, uint32_t write_addr,
                                 uint16_t num_byte_to_write) {
    union GD32_UINT32_DATA_TYPE addr;

    addr.Uint32_Data = write_addr;

    spi_flash_write_enable();

    SPI_FLASH_NSS_ENABLE;

    spi_SendRcvByte(CMD_PAGE_PROGRAM);
    spi_SendRcvByte(addr.b[2]);
    spi_SendRcvByte(addr.b[1]);
    spi_SendRcvByte(addr.b[0]);
    while (num_byte_to_write--) {
        spi_SendRcvByte(*pbuffer);
        pbuffer++;
    }

    SPI_FLASH_NSS_DISENABLE;

    spi_flash_wait_for_write_end();
}

void spi_flash_buffer_write(uint8_t *pbuffer, uint32_t write_addr,
                            uint16_t len) {
    uint8_t num_of_page = 0, num_of_single = 0, addr = 0, count = 0, temp = 0;

    addr = write_addr % SPI_FLASH_PAGE_SIZE;
    count = SPI_FLASH_PAGE_SIZE - addr;
    num_of_page = len / SPI_FLASH_PAGE_SIZE;
    num_of_single = len % SPI_FLASH_PAGE_SIZE;

    if (0 == addr) {
        if (0 == num_of_page)
            spi_flash_page_write(pbuffer, write_addr, len);
        else {
            while (num_of_page--) {
                spi_flash_page_write(pbuffer, write_addr, SPI_FLASH_PAGE_SIZE);
                write_addr += SPI_FLASH_PAGE_SIZE;
                pbuffer += SPI_FLASH_PAGE_SIZE;
            }
            spi_flash_page_write(pbuffer, write_addr, num_of_single);
        }
    } else {
        if (0 == num_of_page) {
            if (num_of_single > count) {
                temp = num_of_single - count;
                spi_flash_page_write(pbuffer, write_addr, count);
                write_addr += count;
                pbuffer += count;
                spi_flash_page_write(pbuffer, write_addr, temp);
            } else
                spi_flash_page_write(pbuffer, write_addr, len);
        } else {
            len -= count;
            num_of_page = len / SPI_FLASH_PAGE_SIZE;
            num_of_single = len % SPI_FLASH_PAGE_SIZE;

            spi_flash_page_write(pbuffer, write_addr, count);
            write_addr += count;
            pbuffer += count;

            while (num_of_page--) {
                spi_flash_page_write(pbuffer, write_addr, SPI_FLASH_PAGE_SIZE);
                write_addr += SPI_FLASH_PAGE_SIZE;
                pbuffer += SPI_FLASH_PAGE_SIZE;
            }

            if (0 != num_of_single)
                spi_flash_page_write(pbuffer, write_addr, num_of_single);
        }
    }
}

void spi_read_cfg(dev_cfg_t *_cfg) {
    __disable_irq();
    get_ReadDataBytes(0x0, (uint8_t *)_cfg, sizeof(dev_cfg_t));
    __enable_irq();
}

void spi_write_cfg(dev_cfg_t *_cfg) {
    // printf("write to flash\r\n");
    _cfg->crc_valid = CRC16_Calculate((uint8_t *)_cfg, sizeof(dev_cfg_t) - 2);
    __disable_irq();
    spi_flash_sector_erase(0x0);
    spi_flash_buffer_write((uint8_t *)_cfg, 0x0, sizeof(dev_cfg_t));
    __enable_irq();
}
